/*
 * Copyright 2014, NuzoweR@Schneefeuer <nuzower@gmail.com>.
 * The Settlers Online - Combat Simulator (tsosim).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Garrison.h"
#include "Statistics.h"

#include <iostream>
#include <set>
#include <sstream>
#include <stdexcept>


typedef std::map<std::string, Garrison> GarrisonMap_t;

static GarrisonMap_t garrison_map;


Garrison* Garrison::create(std::string const& id, int tower_bonus)
{
	GarrisonMap_t::iterator iter = garrison_map.find(id);
	if(iter != garrison_map.end())
	{
		// garrison with this id already exists
		return NULL;
	}

	Garrison* g = &garrison_map[id];
	g->set_name(id);
	g->setTowerBonus(tower_bonus);

	return g;
}

Garrison* Garrison::get(std::string const& id)
{
	GarrisonMap_t::iterator it = garrison_map.find(id);
	if(it == garrison_map.end()) {
		return NULL;
	}
	return &it->second;
}

Garrison::Garrison(const Garrison& g)
{
	m_name       = g.m_name;

	m_tower_bonus= g.m_tower_bonus;

	// copy unit data
	m_units      = g.m_units;

	// copy unit pointers
	m_unit_order = g.m_unit_order;

	// recreate group pointers, as in a simple copy, the pointers would have pointed to units from 'g'
	update_group_order();
}

void Garrison::init_stats(Statistics* stats)
{
	if(!stats) return;

	int num_units = m_units.size();

	stats->unit_data.clear();
	stats->unit_data.resize(num_units);

	update_group_order();

	GroupOrder_t::const_iterator cit = m_group_order.begin();
	for(int i=0; cit != m_group_order.end(); ++cit, ++i)
	{
		stats->unit_index[(*cit)->unit] = i;
		stats->unit_data[i].unit         = (*cit)->unit;
		stats->unit_data[i].start_number = (*cit)->number;
	}

	//UnitsMap_t::const_iterator cit = m_units.begin();
	//for(int i=0; cit != m_units.end(); ++cit, ++i)
	//{
	//	stats->unit_index[cit->second.unit] = i;
	//	stats->unit_data[i].unit         = cit->second.unit;
	//	stats->unit_data[i].start_number = cit->second.number;
	//}
}

void Garrison::update_stats(Statistics* stats, STAT_UPDATE su)
{
	if(!stats) return;

	//GroupOrder_t::const_iterator cit = m_group_order.begin();
	//for(; cit != m_group_order.begin(); ++cit)
	//{
	//	Statistics::UnitIndexMap_t::iterator unit_it = stats->unit_index.find(cit);

	//	if(unit_it == stats->unit_index.end()) {
	//		throw std::runtime_error("unkown unit to process during statistics update!");
	//	}

	//	Statistics::Data& data = stats->unit_data[unit_it->second];
	//	if(su == NORMAL) {
	//		data.iteration_results.push_back(cit->second.number);
	//		data.number_iterations += 1;
	//	}
	//	else if(su == MIN) {
	//		data.results.min = cit->second.number;
	//	}
	//	else if(su == MAX) {
	//		data.results.max = cit->second.number;
	//	}
	//}

	UnitsMap_t::const_iterator cit = m_units.begin();
	for(; cit != m_units.end(); ++cit)
	{
		Statistics::UnitIndexMap_t::iterator unit_it = stats->unit_index.find(cit->second.unit);

		if(unit_it == stats->unit_index.end()) {
			throw std::runtime_error("unkown unit to process during statistics update!");
		}

		Statistics::Data& data = stats->unit_data[unit_it->second];
		if(su == NORMAL) {
			data.iteration_results.push_back(cit->second.number);
			data.number_iterations += 1;
		}
		else if(su == MIN) {
			data.results.min = cit->second.number;
		}
		else if(su == MAX) {
			data.results.max = cit->second.number;
		}
	}
}

bool Garrison::add_units(const Unit* unit, int number) 
{ 
	UnitsMap_t::iterator it = m_units.find(unit);
	if(it != m_units.end()) {
		// unit already in garrison
		return false;
	}
	else {
		m_units[unit] = Group();
		it = m_units.find(unit);
	}

	Group& g = it->second;
	g.unit                  = unit;
	g.number                = number;
	g.number_after_attack   = number;
	g.number_left_to_attack = number;
	g.hitpoints.resize(number, g.unit->hitpoints);

	return true;
}

void Garrison::update_group_order(GroupOrder_t& group, UnitOrder_t& unordered_units)
{
	if(m_unit_order.empty())
	{
		// use default unit order
		group.clear();

		// copy pointers to groups
		UnitsMap_t::iterator iter = m_units.begin();
		for(; iter != m_units.end(); ++iter)
			group.push_back(&iter->second);

		// sort according to attack_id
		group.sort(&Garrison::sort_by_attack_id);
	}
	else
	{
		std::set<const Unit*> _units;
		for(UnitsMap_t::iterator it = m_units.begin(); it!=m_units.end(); ++it)
			_units.insert(it->first);

		UnitOrder_t::const_iterator cit = m_unit_order.begin();
		for(; cit != m_unit_order.end(); ++cit)
		{
			UnitsMap_t::iterator ig = m_units.find(*cit);
			if(ig != m_units.end()) {
				group.push_back(&(ig->second));

				std::set<const Unit*>::const_iterator sit = _units.find(*cit);
				if(sit != _units.end()) _units.erase(sit);
				else throw std::runtime_error("");
			}
		}

		if(!_units.empty()) {
			unordered_units.insert(unordered_units.end(), _units.begin(), _units.end());
		}
	}
}


bool Garrison::set_unit_order(std::list<const Unit*> const& order, std::string& error)
{
	m_unit_order.clear();
	m_unit_order.insert(m_unit_order.end(), order.begin(), order.end());

	// check if all garrison units are ordered
	UnitOrder_t units;
	update_group_order(m_group_order, units);

	if(units.empty()) return true;

	// error handling
	std::stringstream ss;
	ss << "Garrison units not in the order : ";
	UnitOrder_t::const_iterator citer = units.begin();
	for(; citer != units.end(); ++citer) {
		ss << " '" << (*citer)->name << "'";
	}

	m_unit_order.clear();
	error = ss.str();
	return false;
}
	
bool Garrison::sort_by_hitpoints(const Group* g1, const Group* g2)
{
	if     ( (g1->unit->skills.hasSkill(Skill::GENERAL)) )
		return false; // general always comes last
	else if( (g2->unit->skills.hasSkill(Skill::GENERAL)) )
		return true;  // general always comes last
	else if( g1->unit->hitpoints == g2->unit->hitpoints )
		return sort_by_attack_id(g1, g2);
	else
		return g1->unit->hitpoints < g2->unit->hitpoints;
}

bool Garrison::sort_by_attack_id(const Group* g1, const Group* g2)
{
	if     ( (g1->unit->skills.hasSkill(Skill::GENERAL)) )
		return false; // general always comes last
	else if( (g2->unit->skills.hasSkill(Skill::GENERAL)) )
		return true;  // general always comes last
	else 
		return g1->unit->attack_id < g2->unit->attack_id;
}

Garrison::GroupOrder_t Garrison::get_defend_list_weakest()
{
	update_group_order();

	GroupOrder_t og_weak, og_rest;
	GroupOrder_t::iterator iter = m_group_order.begin();
	for(; iter != m_group_order.end(); ++iter)
	{
		if( ((*iter)->unit->skills.hasSkill(Skill::WEAK)) ) {
			og_weak.push_back(*iter);
		}
		else {
			og_rest.push_back(*iter);
		}

	}

	og_weak.sort(&Garrison::sort_by_hitpoints);
	//og_rest.sort(&Garrison::sort_by_hitpoints);

	GroupOrder_t og;
	og.insert(og.end(), og_weak.begin(), og_weak.end());
	og.insert(og.end(), og_rest.begin(), og_rest.end());

	return og;
}

Garrison::GroupOrder_t Garrison::get_defend_list_normal()
{
	update_group_order();

	return m_group_order;
}


void Garrison::update_group_order()
{
	if(m_group_order.empty()) {
		UnitOrder_t units;
		update_group_order(m_group_order, units);

		if(!units.empty()) {
			throw std::runtime_error("Unexpected error : there are unordered units in this garrison!!!");
		}
	}
}

Garrison::GroupOrder_t Garrison::get_attack_list_by_initiative(Initiative::EInitiative type)
{
	update_group_order();

	GroupOrder_t og;

	GroupOrder_t::iterator iter = m_group_order.begin();
	for(; iter != m_group_order.end(); ++iter)
	{
		if((*iter)->unit->initiative == type) {
			og.push_back(*iter);
		}
	}

	og.sort(&Garrison::sort_by_attack_id);
	return og;
}


/**
	* set number of units to the number left units after a fight.
	* couldn't have set it immediately, because need original number of units for counter-attack
	*/
void Garrison::update_garrison_after_initiative()
{
	UnitsMap_t::iterator iter = m_units.begin();
	for(; iter != m_units.end(); ++iter)
	{
		iter->second.number                = iter->second.number_after_attack;
		iter->second.number_left_to_attack = iter->second.number_after_attack;
	}
}

void Garrison::update_garrison_after_combat()
{
	UnitsMap_t::iterator iter = m_units.begin();
	for(; iter != m_units.end(); ++iter)
	{
		for(unsigned int i=0; i < iter->second.hitpoints.size(); ++i)
		{
			// if a unit is (damaged, but) alive, 
			// then restore its hitpoints to the normal level
			if(iter->second.hitpoints[i] > 0) 
			{
				iter->second.hitpoints[i] = iter->second.unit->hitpoints;
			}
		}
	}
}


int Garrison::has_units_with_hitpoints() const
{
	int number_units = 0;
	UnitsMap_t::const_iterator iter = m_units.begin();
	for(; iter != m_units.end(); ++iter)
	{
		number_units += iter->second.number;
	}

	return number_units;
}
